package ethclient
  
import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math/big"

	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
)

type Contract struct {
	rpc *Ext
	abi *abi.ABI
	address common.Address
}

func(ext *Ext) NewContract(abiJson []byte,addr common.Address) (*Contract, error) {
	r := bytes.NewReader(abiJson)
	a, err := abi.JSON(r)
	if err != nil {
		return nil,fmt.Errorf("read abi failure %v",err)
	}
	return &Contract{
		abi: &a,
		rpc: ext,
		address: addr,
	}, nil
}

func(contract *Contract) Close() {
	contract.rpc.eth.Close()
}

func(contract *Contract) Address() common.Address { return contract.address }
func(contract *Contract) Call(v interface{}, from common.Address, 
	functionName string, args ...interface{}) (interface{}, error) {

	data,err := contract.abi.Pack(functionName,args...)
	if err != nil {
		return nil,fmt.Errorf("pack args failure %v",err)
	}
	nonce := contract.rpc.Nonce()
	tx,err := contract.rpc.FillTx(&nonce,big.NewInt(0))
	if err != nil {
		return nil,fmt.Errorf("fill tx failure %v",err)
	}

	tx.SetData(data)
	tx.SetTo(contract.address)

	msg := tx.ToMsg(from)
	hex,err := contract.rpc.eth.CallContract(contract.rpc.ctx,msg,nil)
	if err != nil {
		return nil, err
	}
	err = contract.abi.Unpack(v,functionName,hex) 

	return v,err
}

func(contract *Contract) Deploy(nonce uint64,code []byte,args ...interface{}) (*common.Hash,error) {
	data,err := contract.abi.Pack("",args...)
	if err != nil {
		return nil,fmt.Errorf("Pack args failure, %v",err)
	} 
	cdata := append(code,data...)
	return contract.rpc.SendTx(nonce,nil,cdata,nil)
}

func(contract *Contract) CallEx(nonce uint64,functionName string,args ...interface{}) (*common.Hash,error) {
	data,err := contract.abi.Pack(functionName,args...)
	if err != nil {
		return nil,fmt.Errorf("Pack args failure, %v",err)
	} 
	return contract.rpc.SendTx(nonce,&contract.address,data,nil)
}

func BinFile(binFile string) ([]byte,error) {
	content, err := ioutil.ReadFile(binFile)
	if err != nil {
		return []byte{},fmt.Errorf("read file failure, %v",err)
	}
	return Bin(content)
}

func Bin(content []byte) ([]byte,error) {
	
	type ContractFile struct {
		Bytecode string `json:"object"`
	}

	var jsonResponse ContractFile

	json.Unmarshal(content, &jsonResponse)

	return common.Hex2Bytes(jsonResponse.Bytecode),nil
}